// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
#include "gpu/command_buffer/service/gles2_cmd_decoder_unittest.h"

using ::gfx::MockGLInterface;
using ::testing::_;
using ::testing::Return;
using ::testing::SetArgPointee;

namespace gpu {
namespace gles2 {

    using namespace cmds;

    namespace {

    } // namespace anonymous

    TEST_P(GLES2DecoderTest, MapBufferRangeUnmapBufferReadSucceeds)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        const GLbitfield kAccess = GL_MAP_READ_BIT;

        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        // uint32_t is Result for both MapBufferRange and UnmapBuffer commands.
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(uint32_t);

        DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);

        std::vector<int8_t> data(kSize);
        for (GLsizeiptr ii = 0; ii < kSize; ++ii) {
            data[ii] = static_cast<int8_t>(ii % 255);
        }

        { // MapBufferRange
            EXPECT_CALL(*gl_,
                MapBufferRange(kTarget, kOffset, kSize, kAccess))
                .WillOnce(Return(&data[0]))
                .RetiresOnSaturation();

            typedef MapBufferRange::Result Result;
            Result* result = GetSharedMemoryAs<Result*>();

            MapBufferRange cmd;
            cmd.Init(kTarget, kOffset, kSize, kAccess, data_shm_id, data_shm_offset,
                result_shm_id, result_shm_offset);
            decoder_->set_unsafe_es3_apis_enabled(false);
            *result = 0;
            EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
            EXPECT_EQ(0u, *result);
            decoder_->set_unsafe_es3_apis_enabled(true);
            *result = 0;
            EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
            int8_t* mem = reinterpret_cast<int8_t*>(&result[1]);
            EXPECT_EQ(0, memcmp(&data[0], mem, kSize));
            EXPECT_EQ(1u, *result);
        }

        { // UnmapBuffer
            EXPECT_CALL(*gl_, UnmapBuffer(kTarget))
                .WillOnce(Return(GL_TRUE))
                .RetiresOnSaturation();

            UnmapBuffer cmd;
            cmd.Init(kTarget);
            decoder_->set_unsafe_es3_apis_enabled(false);
            EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
            decoder_->set_unsafe_es3_apis_enabled(true);
            EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
        }

        EXPECT_EQ(GL_NO_ERROR, GetGLError());
    }

    TEST_P(GLES2DecoderTest, MapBufferRangeUnmapBufferWriteSucceeds)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        const GLbitfield kAccess = GL_MAP_WRITE_BIT;
        const GLbitfield kMappedAccess = GL_MAP_WRITE_BIT | GL_MAP_READ_BIT;

        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        // uint32_t is Result for both MapBufferRange and UnmapBuffer commands.
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(uint32_t);

        DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);

        std::vector<int8_t> data(kSize);
        for (GLsizeiptr ii = 0; ii < kSize; ++ii) {
            data[ii] = static_cast<int8_t>(ii % 255);
        }

        { // MapBufferRange succeeds
            EXPECT_CALL(*gl_,
                MapBufferRange(kTarget, kOffset, kSize, kMappedAccess))
                .WillOnce(Return(&data[0]))
                .RetiresOnSaturation();

            typedef MapBufferRange::Result Result;
            Result* result = GetSharedMemoryAs<Result*>();

            MapBufferRange cmd;
            cmd.Init(kTarget, kOffset, kSize, kAccess, data_shm_id, data_shm_offset,
                result_shm_id, result_shm_offset);
            decoder_->set_unsafe_es3_apis_enabled(false);
            *result = 0;
            EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
            EXPECT_EQ(0u, *result);
            decoder_->set_unsafe_es3_apis_enabled(true);
            *result = 0;
            EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
            int8_t* mem = reinterpret_cast<int8_t*>(&result[1]);
            EXPECT_EQ(0, memcmp(&data[0], mem, kSize));
            EXPECT_EQ(1u, *result);
        }

        { // UnmapBuffer succeeds
            EXPECT_CALL(*gl_, UnmapBuffer(kTarget))
                .WillOnce(Return(GL_TRUE))
                .RetiresOnSaturation();

            UnmapBuffer cmd;
            cmd.Init(kTarget);
            decoder_->set_unsafe_es3_apis_enabled(false);
            EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
            decoder_->set_unsafe_es3_apis_enabled(true);
            EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
        }

        EXPECT_EQ(GL_NO_ERROR, GetGLError());
    }

    TEST_P(GLES2DecoderTest, MapBufferRangeNotInitFails)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        const GLbitfield kAccess = GL_MAP_READ_BIT;
        std::vector<int8_t> data(kSize);

        typedef MapBufferRange::Result Result;
        Result* result = GetSharedMemoryAs<Result*>();
        *result = 1; // Any value other than 0.
        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(*result);

        MapBufferRange cmd;
        cmd.Init(kTarget, kOffset, kSize, kAccess, data_shm_id, data_shm_offset,
            result_shm_id, result_shm_offset);
        decoder_->set_unsafe_es3_apis_enabled(true);
        EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
    }

    TEST_P(GLES2DecoderTest, MapBufferRangeWriteInvalidateRangeSucceeds)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        // With MAP_INVALIDATE_RANGE_BIT, no need to append MAP_READ_BIT.
        const GLbitfield kAccess = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT;

        DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);

        std::vector<int8_t> data(kSize);
        for (GLsizeiptr ii = 0; ii < kSize; ++ii) {
            data[ii] = static_cast<int8_t>(ii % 255);
        }
        EXPECT_CALL(*gl_,
            MapBufferRange(kTarget, kOffset, kSize, kAccess))
            .WillOnce(Return(&data[0]))
            .RetiresOnSaturation();

        typedef MapBufferRange::Result Result;
        Result* result = GetSharedMemoryAs<Result*>();
        *result = 0;
        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(*result);

        int8_t* mem = reinterpret_cast<int8_t*>(&result[1]);
        memset(mem, 72, kSize); // Init to a random value other than 0.

        MapBufferRange cmd;
        cmd.Init(kTarget, kOffset, kSize, kAccess, data_shm_id, data_shm_offset,
            result_shm_id, result_shm_offset);
        decoder_->set_unsafe_es3_apis_enabled(true);
        EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    }

    TEST_P(GLES2DecoderTest, MapBufferRangeWriteInvalidateBufferSucceeds)
    {
        // Test INVALIDATE_BUFFER_BIT is mapped to INVALIDATE_RANGE_BIT.
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        const GLbitfield kAccess = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT;
        // With MAP_INVALIDATE_BUFFER_BIT, no need to append MAP_READ_BIT.
        const GLbitfield kFilteredAccess = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT;

        DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);

        std::vector<int8_t> data(kSize);
        for (GLsizeiptr ii = 0; ii < kSize; ++ii) {
            data[ii] = static_cast<int8_t>(ii % 255);
        }
        EXPECT_CALL(*gl_,
            MapBufferRange(kTarget, kOffset, kSize, kFilteredAccess))
            .WillOnce(Return(&data[0]))
            .RetiresOnSaturation();

        typedef MapBufferRange::Result Result;
        Result* result = GetSharedMemoryAs<Result*>();
        *result = 0;
        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(*result);

        int8_t* mem = reinterpret_cast<int8_t*>(&result[1]);
        memset(mem, 72, kSize); // Init to a random value other than 0.

        MapBufferRange cmd;
        cmd.Init(kTarget, kOffset, kSize, kAccess, data_shm_id, data_shm_offset,
            result_shm_id, result_shm_offset);
        decoder_->set_unsafe_es3_apis_enabled(true);
        EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    }

    TEST_P(GLES2DecoderTest, MapBufferRangeWriteUnsynchronizedBit)
    {
        // Test UNSYNCHRONIZED_BIT is filtered out.
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        const GLbitfield kAccess = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
        const GLbitfield kFilteredAccess = GL_MAP_WRITE_BIT | GL_MAP_READ_BIT;

        DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);

        std::vector<int8_t> data(kSize);
        for (GLsizeiptr ii = 0; ii < kSize; ++ii) {
            data[ii] = static_cast<int8_t>(ii % 255);
        }
        EXPECT_CALL(*gl_,
            MapBufferRange(kTarget, kOffset, kSize, kFilteredAccess))
            .WillOnce(Return(&data[0]))
            .RetiresOnSaturation();

        typedef MapBufferRange::Result Result;
        Result* result = GetSharedMemoryAs<Result*>();
        *result = 0;
        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(*result);

        int8_t* mem = reinterpret_cast<int8_t*>(&result[1]);
        memset(mem, 72, kSize); // Init to a random value other than 0.

        MapBufferRange cmd;
        cmd.Init(kTarget, kOffset, kSize, kAccess, data_shm_id, data_shm_offset,
            result_shm_id, result_shm_offset);
        decoder_->set_unsafe_es3_apis_enabled(true);
        EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
        EXPECT_EQ(0, memcmp(&data[0], mem, kSize));
    }

    TEST_P(GLES2DecoderTest, MapBufferRangeWithError)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        const GLbitfield kAccess = GL_MAP_READ_BIT;
        std::vector<int8_t> data(kSize);
        for (GLsizeiptr ii = 0; ii < kSize; ++ii) {
            data[ii] = static_cast<int8_t>(ii % 255);
        }
        EXPECT_CALL(*gl_,
            MapBufferRange(kTarget, kOffset, kSize, kAccess))
            .WillOnce(Return(nullptr)) // Return nullptr to indicate a GL error.
            .RetiresOnSaturation();

        typedef MapBufferRange::Result Result;
        Result* result = GetSharedMemoryAs<Result*>();
        *result = 0;
        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(*result);

        int8_t* mem = reinterpret_cast<int8_t*>(&result[1]);
        memset(mem, 72, kSize); // Init to a random value other than 0.

        MapBufferRange cmd;
        cmd.Init(kTarget, kOffset, kSize, kAccess, data_shm_id, data_shm_offset,
            result_shm_id, result_shm_offset);
        decoder_->set_unsafe_es3_apis_enabled(true);
        EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
        memset(&data[0], 72, kSize);
        // Mem is untouched.
        EXPECT_EQ(0, memcmp(&data[0], mem, kSize));
        EXPECT_EQ(0u, *result);
    }

    TEST_P(GLES2DecoderTest, MapBufferRangeBadSharedMemoryFails)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        const GLbitfield kAccess = GL_MAP_READ_BIT;
        std::vector<int8_t> data(kSize);
        for (GLsizeiptr ii = 0; ii < kSize; ++ii) {
            data[ii] = static_cast<int8_t>(ii % 255);
        }

        typedef MapBufferRange::Result Result;
        Result* result = GetSharedMemoryAs<Result*>();
        *result = 0;
        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(*result);

        decoder_->set_unsafe_es3_apis_enabled(true);
        MapBufferRange cmd;
        cmd.Init(kTarget, kOffset, kSize, kAccess,
            kInvalidSharedMemoryId, data_shm_offset,
            result_shm_id, result_shm_offset);
        EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
        cmd.Init(kTarget, kOffset, kSize, kAccess,
            data_shm_id, data_shm_offset,
            kInvalidSharedMemoryId, result_shm_offset);
        EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
        cmd.Init(kTarget, kOffset, kSize, kAccess,
            data_shm_id, kInvalidSharedMemoryOffset,
            result_shm_id, result_shm_offset);
        EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
        cmd.Init(kTarget, kOffset, kSize, kAccess,
            data_shm_id, data_shm_offset,
            result_shm_id, kInvalidSharedMemoryOffset);
        EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
    }

    TEST_P(GLES2DecoderTest, UnmapBufferWriteNotMappedFails)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;

        DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);

        UnmapBuffer cmd;
        cmd.Init(kTarget);
        decoder_->set_unsafe_es3_apis_enabled(true);
        EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
        EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
    }

    TEST_P(GLES2DecoderTest, UnmapBufferWriteNoBoundBufferFails)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;

        UnmapBuffer cmd;
        cmd.Init(kTarget);
        decoder_->set_unsafe_es3_apis_enabled(true);
        EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
        EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
    }

    TEST_P(GLES2DecoderTest, BufferDataDestroysDataStore)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        const GLbitfield kAccess = GL_MAP_WRITE_BIT;
        const GLbitfield kFilteredAccess = GL_MAP_WRITE_BIT | GL_MAP_READ_BIT;

        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        // uint32_t is Result for both MapBufferRange and UnmapBuffer commands.
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(uint32_t);

        DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);

        std::vector<int8_t> data(kSize);

        decoder_->set_unsafe_es3_apis_enabled(true);

        { // MapBufferRange succeeds
            EXPECT_CALL(*gl_,
                MapBufferRange(kTarget, kOffset, kSize, kFilteredAccess))
                .WillOnce(Return(&data[0]))
                .RetiresOnSaturation();

            typedef MapBufferRange::Result Result;
            Result* result = GetSharedMemoryAs<Result*>();

            MapBufferRange cmd;
            cmd.Init(kTarget, kOffset, kSize, kAccess, data_shm_id, data_shm_offset,
                result_shm_id, result_shm_offset);
            *result = 0;
            EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
            EXPECT_EQ(1u, *result);
        }

        { // BufferData unmaps the data store.
            DoBufferData(kTarget, kSize * 2);
            EXPECT_EQ(GL_NO_ERROR, GetGLError());
        }

        { // UnmapBuffer fails.
            UnmapBuffer cmd;
            cmd.Init(kTarget);
            EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
            EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
        }
    }

    TEST_P(GLES2DecoderTest, DeleteBuffersDestroysDataStore)
    {
        const GLenum kTarget = GL_ARRAY_BUFFER;
        const GLintptr kOffset = 10;
        const GLsizeiptr kSize = 64;
        const GLbitfield kAccess = GL_MAP_WRITE_BIT;
        const GLbitfield kFilteredAccess = GL_MAP_WRITE_BIT | GL_MAP_READ_BIT;

        uint32_t result_shm_id = kSharedMemoryId;
        uint32_t result_shm_offset = kSharedMemoryOffset;
        uint32_t data_shm_id = kSharedMemoryId;
        // uint32_t is Result for both MapBufferRange and UnmapBuffer commands.
        uint32_t data_shm_offset = kSharedMemoryOffset + sizeof(uint32_t);

        DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);

        std::vector<int8_t> data(kSize);

        decoder_->set_unsafe_es3_apis_enabled(true);

        { // MapBufferRange succeeds
            EXPECT_CALL(*gl_,
                MapBufferRange(kTarget, kOffset, kSize, kFilteredAccess))
                .WillOnce(Return(&data[0]))
                .RetiresOnSaturation();

            typedef MapBufferRange::Result Result;
            Result* result = GetSharedMemoryAs<Result*>();

            MapBufferRange cmd;
            cmd.Init(kTarget, kOffset, kSize, kAccess, data_shm_id, data_shm_offset,
                result_shm_id, result_shm_offset);
            *result = 0;
            EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
            EXPECT_EQ(1u, *result);
        }

        { // DeleteBuffers unmaps the data store.
            DoDeleteBuffer(client_buffer_id_, kServiceBufferId);
            EXPECT_EQ(GL_NO_ERROR, GetGLError());
        }

        { // UnmapBuffer fails.
            UnmapBuffer cmd;
            cmd.Init(kTarget);
            EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
            EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
        }
    }

} // namespace gles2
} // namespace gpu
